import matplotlib
import parselmouth
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import librosa
from librosa.display import waveplot
import pandas as pd
from matplotlib import gridspec
from essentia.standard import TonicIndianArtMusic
from IPython.display import Audio
sns.set_theme()
min_pitch = 165
max_pitch = 440
start_time = 500
end_time = 530
audio_path = 'Data/NIR_VS_Bhoop_Vox.wav'
cycle_file = 'Data/NIR_VS_Bhoop_Metre_VilambitEktal.csv'
Extract tonic or Sa of the entire song (in Hz) using the module - essentia
audio, sr = librosa.load(audio_path, sr=None, mono=True)
tonic = TonicIndianArtMusic(sampleRate=sr, minTonicFrequency=200)(audio)
We load only the relevant part of the audio file here. Audio is loaded in mono, and sample rate is unchanged.
The frequencies sung are extracted using a software called Praat.
audio, sr = librosa.load(audio_path, sr=None, mono=True, offset=start_time, duration = end_time - start_time)
snd = parselmouth.Sound(audio, sr)
pitch = snd.to_pitch_ac(0.01, min_pitch, 15, True, 0.03, 0.45, 0.01, 0.9, 0.14, max_pitch)
cycle_df = pd.read_csv(cycle_file)
index_values = cycle_df.loc[(cycle_df['Time'] >= start_time) & (cycle_df['Time'] <= end_time)].index.values
vibhaags = cycle_df.iloc[max(index_values[0]-1, 0):min(index_values[-1]+2, cycle_df.shape[0])]
# add all matras - each vibhaag has 4 matras
matras = []
for ind, vibhaag in enumerate(vibhaags['Time'].values[:-1]):
matras.extend(np.around(np.linspace(vibhaag, vibhaags['Time'].values[ind+1], num = 4, endpoint=False), 2)[1:])
Play the audio being analysed
Audio(audio, rate=sr)
Plotting the pitch contour. Matras are marked with a vertical line - solid line indicating a vibhaag and dotted line indicating a matra; and the cycle number is noted in a box at the bottom of the line.
Plot with Hz on y-axis
yNoteCents = np.array([-500, -300, 0, 400, 700, 900, 1200, 1600, 1900])
yNoteHz = np.array([164.81, 185, 220, 277.18, 329.63, 369.99, 440, 554.37, 659.25])
yNotes = np.array(["P_", "D_", "S", "G", "P", "D", "S'", "G'", "P'"])
xvals = pitch.xs()
yvals = pitch.selected_array['frequency']
yvals[yvals==0] = np.nan
fig = plt.figure(figsize=(14, 7))
specs = fig.add_gridspec(2, 1, height_ratios = [1, 2])
axs = [fig.add_subplot(specs[0, 0]), fig.add_subplot(specs[1, 0])]
#plot the waveform
waveplot(audio, sr, ax=axs[0])
axs[0].set(title='Waveform')
#plot pitch contour
axs[1] = sns.lineplot(x=xvals, y=yvals, ax=axs[1])
axs[1].set(xlabel='Time Stamp (s)', ylabel='Notes', title='Pitch Contour (in Hz)')
axs[1].set_xticks(axs[1].get_xticks())
axs[1].set_xticklabels(axs[1].get_xticks() + start_time)
axs[0].set_xticks([])
axs[0].set_xlim(axs[1].get_xlim())
for i, vibhaag in vibhaags.iterrows():
#plot vibhaags
if vibhaag['Time'] >= start_time and vibhaag['Time'] <= end_time:
axs[0].axvline(vibhaag['Time']-start_time, linestyle='-', c='purple')
axs[1].axvline(vibhaag['Time']-start_time, linestyle='-', c='purple')
axs[1].annotate(vibhaag['Cycle'], (vibhaag['Time']-start_time, min(yvals)+10), bbox=dict(facecolor='grey', edgecolor='white'), c='white')
for matra in matras:
if matra >= start_time and matra <= end_time:
axs[0].axvline(matra-start_time, ymin=0.25, ymax=0.75, linestyle='--', c='purple')
axs[1].axvline(matra-start_time, ymin=0.25, ymax=0.75, linestyle='--', c='purple')
axs[1].set_yticks(yNoteHz[(yNoteHz >= min(yvals)) & (yNoteHz <= max(yvals))])
axs[1].set_yticklabels(yNotes[(yNoteHz >= min(yvals)) & (yNoteHz <= max(yvals))])
fig.tight_layout()
Plot with Cents on the y-axis
xvals = pitch.xs()
yvals = pitch.selected_array['frequency']
yvals[yvals==0] = np.nan
yvals[~(np.isnan(yvals))] = 1200*np.log2(yvals[~(np.isnan(yvals))]/tonic)
fig = plt.figure(figsize=(14, 7))
specs = fig.add_gridspec(2, 1, height_ratios = [1, 2])
axs = [fig.add_subplot(specs[0, 0]), fig.add_subplot(specs[1, 0])]
#plot the waveform
waveplot(audio, sr, ax=axs[0])
axs[0].set(title='Waveform')
#plot pitch contour
axs[1] = sns.lineplot(x=xvals, y=yvals, ax=axs[1])
axs[1].set(xlabel='Time Stamp (s)', ylabel='Notes', title='Pitch Contour (in Cents)')
axs[1].set_xticks(axs[1].get_xticks())
axs[1].set_xticklabels(axs[1].get_xticks() + start_time)
axs[0].set_xticks([])
axs[0].set_xlim(axs[1].get_xlim())
for i, vibhaag in vibhaags.iterrows():
#plot vibhaags
if vibhaag['Time'] >= start_time and vibhaag['Time'] <= end_time:
axs[0].axvline(vibhaag['Time']-start_time, linestyle='-', c='purple')
axs[1].axvline(vibhaag['Time']-start_time, linestyle='-', c='purple')
axs[1].annotate(vibhaag['Cycle'], (vibhaag['Time']-start_time, min(yvals)+10), bbox=dict(facecolor='grey', edgecolor='white'), c='white')
for matra in matras:
if matra >= start_time and matra <= end_time:
axs[0].axvline(matra-start_time, ymin=0.25, ymax=0.75, linestyle='--', c='purple')
axs[1].axvline(matra-start_time, ymin=0.25, ymax=0.75, linestyle='--', c='purple')
axs[1].set_yticks(yNoteCents[(yNoteCents >= min(yvals)) & (yNoteCents <= max(yvals))])
axs[1].set_yticklabels(yNotes[(yNoteCents >= min(yvals)) & (yNoteCents <= max(yvals))])
fig.tight_layout()